//你的国家有无数个湖泊，所有湖泊一开始都是空的。当第 n 个湖泊下雨前是空的，那么它就会装满水。如果第 n 个湖泊下雨前是 满的 ，这个湖泊会发生 洪水 。你
//的目标是避免任意一个湖泊发生洪水。 
//
// 给你一个整数数组 rains ，其中： 
//
// 
// rains[i] > 0 表示第 i 天时，第 rains[i] 个湖泊会下雨。 
// rains[i] == 0 表示第 i 天没有湖泊会下雨，你可以选择 一个 湖泊并 抽干 这个湖泊的水。 
// 
//
// 请返回一个数组 ans ，满足： 
//
// 
// ans.length == rains.length 
// 如果 rains[i] > 0 ，那么ans[i] == -1 。 
// 如果 rains[i] == 0 ，ans[i] 是你第 i 天选择抽干的湖泊。 
// 
//
// 如果有多种可行解，请返回它们中的 任意一个 。如果没办法阻止洪水，请返回一个 空的数组 。 
//
// 请注意，如果你选择抽干一个装满水的湖泊，它会变成一个空的湖泊。但如果你选择抽干一个空的湖泊，那么将无事发生。 
//
// 
//
// 示例 1： 
//
// 
//输入：rains = [1,2,3,4]
//输出：[-1,-1,-1,-1]
//解释：第一天后，装满水的湖泊包括 [1]
//第二天后，装满水的湖泊包括 [1,2]
//第三天后，装满水的湖泊包括 [1,2,3]
//第四天后，装满水的湖泊包括 [1,2,3,4]
//没有哪一天你可以抽干任何湖泊的水，也没有湖泊会发生洪水。
// 
//
// 示例 2： 
//
// 
//输入：rains = [1,2,0,0,2,1]
//输出：[-1,-1,2,1,-1,-1]
//解释：第一天后，装满水的湖泊包括 [1]
//第二天后，装满水的湖泊包括 [1,2]
//第三天后，我们抽干湖泊 2 。所以剩下装满水的湖泊包括 [1]
//第四天后，我们抽干湖泊 1 。所以暂时没有装满水的湖泊了。
//第五天后，装满水的湖泊包括 [2]。
//第六天后，装满水的湖泊包括 [1,2]。
//可以看出，这个方案下不会有洪水发生。同时， [-1,-1,1,2,-1,-1] 也是另一个可行的没有洪水的方案。
// 
//
// 示例 3： 
//
// 
//输入：rains = [1,2,0,1,2]
//输出：[]
//解释：第二天后，装满水的湖泊包括 [1,2]。我们可以在第三天抽干一个湖泊的水。
//但第三天后，湖泊 1 和 2 都会再次下雨，所以不管我们第三天抽干哪个湖泊的水，另一个湖泊都会发生洪水。
// 
//
// 
//
// 提示： 
//
// 
// 1 <= rains.length <= 10⁵ 
// 0 <= rains[i] <= 10⁹ 
// 
//
// Related Topics 贪心 数组 哈希表 二分查找 堆（优先队列） 👍 251 👎 0


package LeetCode.editor.cn;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;

/**
 * @author ldltd
 * @date 2025-10-07 12:03:23
 * @description 1488.避免洪水泛滥
 */
public class AvoidFloodInTheCity{
	 public static void main(String[] args) {
	 	 //测试代码
	 	 AvoidFloodInTheCity fun=new AvoidFloodInTheCity();
	 	 Solution solution = fun.new Solution();
		solution.avoidFlood(new int[]{1,2,0,0,2,1});
	 }
	 
//力扣代码
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
		 // 有序集合+哈希表
	public int[] avoidFlood1(int[] rains) {
		int[] ans = new int[rains.length];
		Arrays.fill(ans, 1);
		TreeSet<Integer> st = new TreeSet<Integer>();
		// key:湖泊编号 value:上次下雨的时间
		Map<Integer, Integer> mp = new HashMap<Integer, Integer>();
		for (int i = 0; i < rains.length; ++i) {
			if (rains[i] == 0) {
				st.add(i);
			} else {
				ans[i] = -1;
				// 如果下雨的湖泊之前下过雨
				if (mp.containsKey(rains[i])) {
					// 在下雨时间之后的可以抽干的时间中找一个最早的
					Integer it = st.ceiling(mp.get(rains[i]));
					// 找不到说明没法避免洪水
					if (it == null) {
						return new int[0];
					}
					// 找到了就把这个时间点的湖泊抽干
					ans[it] = rains[i];
					st.remove(it);
				}
				// 更新湖泊最后下雨时间
				mp.put(rains[i], i);
			}
		}
		return ans;
	}
	// 并查集
	public int[] avoidFlood(int[] rains) {
		int n = rains.length;
		// fa[i] 表示第 i 天之后的第一个未被使用的抽水日
		int[] fa = new int[n + 1];
		// 初始化并查集 ，每一天都是自己的父节点，表示都可以抽水，即未被使用
		for (int i = 0; i <= n; i++) {
			fa[i] = i;
		}

		int[] ans = new int[n];
		Map<Integer, Integer> fullDay = new HashMap<>(); // lake -> 装满日
		for (int i = 0; i < n; i++) {
			int lake = rains[i];
			if (lake == 0) {
				ans[i] = 1; // 先随便选一个湖抽干
				continue;
			}
			Integer j = fullDay.get(lake);
			// 之前没下过雨，直接装满
			if (j != null) {
				// 必须在 j 之后，i 之前把 lake 抽干
				// 选一个最早的未被使用的抽水日，如果选晚的，可能会导致其他湖没有可用的抽水日
				int dryDay = find(j + 1, fa);
				// 不能在 i 之后抽干
				if (dryDay >= i) {
					return new int[]{}; // 无法阻止洪水
				}
				// 在 dryDay 抽干 lake
				ans[dryDay] = lake;
				// dryDay 被使用了，合并到下一个未被使用的抽水日，需要跳过已经被使用的
				// dryDay+1可能已经被使用了，所以要 find
				fa[dryDay] = find(dryDay + 1, fa); // 删除 dryDay
			}
			// 这一天 lake 装满
			ans[i] = -1;
			// 删除 i，等于他本身的可以抽水，否则不行，+1表示去他的下一天找，因为是按时间顺序遍历的
			// 所以fa[i+1] = i + 1，
			fa[i] = i + 1;
			fullDay.put(lake, i); // 插入或更新装满日
		}
		return ans;
	}

	private int find(int x, int[] fa) {
		if (fa[x] != x) {
			fa[x] = find(fa[x], fa);
		}
		return fa[x];
	}

}
//leetcode submit region end(Prohibit modification and deletion)

}
